#include "gtlNetwork.h"
#include "gtlGame.h"

namespace GTL { namespace Engine {

uint32_t ClientConnection::_totalID = 1;

void NetworkService::Start()
{
  Backend->LogInfo("Starting network service");
	
  tn_init();

  mServerListener = 0;
}

void NetworkService::Shutdown()
{
  Backend->LogInfo("Stopping network service");

	mClientConnection.Close();
	for (eastl::list<ClientConnection>::iterator i=mConnections.begin(); i!=mConnections.end(); ++i)
		i->Close();

  tn_destroy_listener(mServerListener);
	tn_shutdown();
}

struct IsDisconnected
{
	bool operator() (const ClientConnection& op)	{ return !op.Connection;	} 
};

int NetworkService::Do( float dt )
{
  tn_connection* conn = 0;

	/// Listen for new connections
  while (mServerListener && (conn = tn_listen(mServerListener)))
  {
    char connAddr[32];
    tn_print_ip(conn->remote_ip, connAddr);
    Backend->LogInfo("Received connection from %s", connAddr);

		mConnections.push_back(ClientConnection(conn));

    mGame->AddEvent(Network::Event::Connection, &mConnections.back(), mConnections.back().ConnectionID);
  }

	/// Check existing connections for incoming packets or disconnections
  for (eastl::list<ClientConnection>::iterator i=mConnections.begin(); i!=mConnections.end(); ++i)
  {
		if (!i->Connection) continue;

    tn_packet* pack = tn_work_connection(i->Connection);


		if (pack)
			mGame->AddEvent(Network::Event::ClientPacket, &(*i), (void*)pack);

    if (i->Connection->state == TNC_DISCONNECTED)
    {
			mGame->AddEvent(Network::Event::Disconnection, 0, i->ConnectionID);
			i->Close();
    }
  }

	/// Remove disconnected connections from list
	mConnections.erase(eastl::remove_if(mConnections.begin(), mConnections.end(), IsDisconnected()), mConnections.end());

	/// Check client connection for packets or disconenction
  if (mClientConnection.Connection)
  {
		int prev_state = mClientConnection.Connection->state;
    
		tn_packet* packet = tn_work_connection(mClientConnection.Connection);

		if (prev_state != TNC_WORKING && mClientConnection.Connection->state == TNC_WORKING)
		{
			mGame->AddEvent(Network::Event::Connected, &mClientConnection, 0);
		}

		if (packet)
		{
			mGame->AddEvent(Network::Event::ServerPacket, &mClientConnection, (void*)packet);
		}

		if (prev_state == TNC_WORKING && mClientConnection.Connection->state != TNC_WORKING)
		{
			mGame->AddEvent(Network::Event::Disconnected, 0, 0);
			mClientConnection.Close();
		}
  }
	return 0;
}

void NetworkService::StartServer( uint16_t port )
{
  Backend->LogInfo("Starting server on port %i", (int)port);

  if (mServerListener)
    tn_destroy_listener(mServerListener);

  tn_address add;
  tn_set_address(&add, "localhost", port);
  mServerListener = tn_create_listener(&add);
  if (!mServerListener)
  {
    int err = tn_get_last_error();
    Backend->LogError("Error creating server listener: %i", err);
  }
}

void NetworkService::StartConnectingToServer( const char* address, uint16_t port )
{
	mClientConnection.Close();

	tn_address addr;
	tn_set_address(&addr, address, port);
	mClientConnection = ClientConnection(tn_connect_to(&addr));
}

ClientConnection* NetworkService::GetConnection( uint32_t id )
{
	for (eastl::list<ClientConnection>::iterator i=mConnections.begin(); i!=mConnections.end(); ++i)
	{
		if (!i->Connection) continue;

		if (i->ConnectionID == id)
			return &(*i);
	}

	return 0;
}

void NetworkService::SendToServer( void* data, uint16_t len )
{
	if (!mClientConnection.Connection) return;

	tn_send_data(mClientConnection.Connection, data, len);
}

void NetworkService::SendToServer( const char* text )
{
	if (!mClientConnection.Connection) return;

	tn_send_string(mClientConnection.Connection, text);
}

void NetworkService::SendToServer( const GTL::Structures::DataNode& node )
{
	if (!mClientConnection.Connection) return;

	eastl::string str = node.Encode();
	tn_send_string(mClientConnection.Connection, str.c_str());
}

void NetworkService::DestroyPacket( tn_packet* packet )
{
	if (!packet) return;

	tn_destroy_packet(packet);
}

ClientConnection& NetworkService::GetClientConnection()
{
	return mClientConnection;
}

void NetworkService::Broadcast( void* data, uint16_t len )
{
	for (eastl::list<ClientConnection>::iterator i=mConnections.begin(); i!=mConnections.end(); ++i)
	{
		if (!i->Connection) continue;

		tn_send_data(i->Connection, data, len);
	}
}

void NetworkService::Broadcast( const char* str )
{
	for (eastl::list<ClientConnection>::iterator i=mConnections.begin(); i!=mConnections.end(); ++i)
	{
		if (!i->Connection) continue;

		tn_send_string(i->Connection, str);
	}
}

void ClientConnection::Close()
{
	if (Connection)
	{
		tn_destroy_connection(Connection);
		Connection = 0;
	}
}

void ClientConnection::SendString( const char* str )
{
	SendData((void*)str, uint16_t(strlen(str)+1));
}

void ClientConnection::SendData( void* data, uint16_t size )
{
	if (tn_send_data(Connection, data, size))
		Backend->LogError("Sending packet of size %i: Network error %i", size, tn_get_last_error());
}

void ClientConnection::SendStringF( const char* str, ... )
{
	char buffer[1024];
	va_list args;
	va_start (args, str);
	int res = vsnprintf(buffer, 1024, str, args);
	va_end (args);
	if (res == -1)
	{
		Backend->LogError("Sending packet with long string exhausted 1024 bytes of buffer for SendStringF");
		return;
	}

	SendData((void*)buffer, uint16_t(res+1));
}
}}